<!DOCTYPE html>
<html lang="zh-CN">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简易Markdown渲染器（修复图片粘贴）</title>
    <style>
        .container {
            display: flex;
            gap: 20px;
            padding: 20px;
            height: calc(100vh - 40px);
            box-sizing: border-box;
        }

        #markdown-input,
        #preview {
            flex: 1;
            height: 100%;
            padding: 15px;
            box-sizing: border-box;
            border: 1px solid #ccc;
            border-radius: 4px;
        }

        #markdown-input {
            font-family: monospace;
            resize: none;
        }

        #preview h1 {
            color: #2c3e50;
            border-bottom: 1px solid #eee;
        }

        #preview strong {
            font-weight: bold;
        }

        #preview em {
            font-style: italic;
            color: #7f8c8d;
        }

        #preview ul,
        #preview ol {
            padding-left: 20px;
            margin: 10px 0;
        }

        /* 优化图片显示样式 */
        #preview img {
            max-width: 100%;
            height: auto;
            margin: 10px 0;
            border-radius: 4px;
            border: 1px solid #eee;
        }
    </style>
</head>

<body>
    <div class="container">
        <div id="markdown-input" placeholder="在这里输入Markdown...（可直接粘贴图片）" contenteditable="true"></div>
        <div id="preview"></div>
    </div>

    <script>
        // 获取DOM元素
        const markdownInput = document.getElementById('markdown-input');
        const preview = document.getElementById('preview');

        // 1. 监听粘贴事件，捕获图片
        markdownInput.addEventListener('paste', (e) => {
            console.log("粘贴触发的数据为:", e);
            // 获取剪切版
            const items = e.clipboardData.items;

            console.log("剪切版上的内容为:", items);

            for (let item of items) {
                // 筛选图片
                if (item.kind === 'file' && item.type.startsWith('image/')) {
                    e.preventDefault(); // 阻止默认粘贴（避免乱码）
                    // 获取图片文件
                    console.log("items中的单个为:", item);
                    const file = item.getAsFile();
                    console.log("使用item.getAsFile()方法后的,图片文件为:", file);
                    // 将file对象数据获取到
                    handleImageInsert(file);
                }
            }
        });

        // 2. 处理图片：转为Base64 + 插入占位符
        // 处理图片：转为Base64 + 插入占位符（适配contenteditable div）
        // 总结: 接受图片文件 file -> 用 FileReader 异步读取到文件为Base64格式
        // 生成tempId 和Markdown 图片站位符 -> 将占位符插入到当前光标位置
        // 缓存 tempId 与 Base64 数据的对应关系
        // 出发预览更新，显示图片

        function handleImageInsert(file) {

            const reader = new FileReader();

            console.log("文件信息:", file);

            reader.onload = (event) => {

                console.log("通过readAsDataURL格式完成读取后的文件", event);

                const base64Url = event.target.result;

                // 生成唯一临时标识
                const tempId = `temp-img-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;

                // 图片占位符语法（Markdown格式）
                const imageSyntax = `![${file.name}](temp://${tempId})\n`;

                // 获取当前选择对象（处理光标位置）
                const selection = window.getSelection();
                console.log("获取当前选择的对象",selection);
                if (selection.rangeCount > 0) {
                    // 获取当前光标所在的范围

                    const range = selection.getRangeAt(0);
                    console.log("当前光标所在范围window.getSelection().getRangeAt",range);
                    
                    // 清除当前选中内容（如果有选中的文本）
                    range.deleteContents();

                    // 创建文本节点插入到光标位置创建一个节点 
                    const textNode = document.createTextNode(imageSyntax);

                    // 插入节点
                    range.insertNode(textNode);
                    
                    // 移动光标到插入内容的后面
                    range.setStartAfter(textNode);
                    // 将当前range的启始位置设置到textNode节点的后面，选择范围的起点
                    range.setEndAfter(textNode);
                    // 让range的启始和结束位置重合,形成一个"空范围",在浏览器中表现光标
                    selection.removeAllRanges();
                    // 清楚当前选择对象中所有的选择范围,浏览器中可能村才多个选择范围,先清楚旧范围,避免光标位置混乱
                    selection.addRange(range);
                    // 将调整好的range添加到选择对象中,光标移动到插入内容的末尾 -> 用户可以直接继续输入，无需手动点击调整位置

                } else {
                    // 如果无法获取光标位置，默认追加到末尾
                    markdownInput.appendChild(document.createTextNode(imageSyntax));

                }

                // 缓存临时图片
                window.tempImages = window.tempImages || {};

                window.tempImages[tempId] = {
                    base64: base64Url,
                    filename: file.name,
                    type: file.type
                };
                console.log("缓存的临时图片", window.tempImages);

                // 触发预览更新
                renderPreviewWithTempImages(markdownInput.innerText);

            };

            // 图片转换错误处理
            reader.onerror = (error) => {
                console.error('图片处理失败：', error);
                alert(`图片粘贴失败：${error.message || '未知错误'}，请检查图片格式或浏览器权限`);
            };

            // 将file文件传入,读取为Base64格式 
            reader.readAsDataURL(file);
        }

        // 3. 带临时图片替换的预览函数
        function renderPreviewWithTempImages(markdown) {

            let previewHtml = renderMarkdown(markdown);
            // 正则转义工具 -> 此处 \\] 第一个是为了将字符串转意符 -> 第二个是让正则引擎看到\] 所以 \\] 最后转为普通字符 ]
            const escapeRegExp = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

            // 替换临时标识为Base64
            Object.keys(window.tempImages || {}).forEach(tempId => {

                const tempUrl = `temp://${tempId}`;

                const base64Url = window.tempImages[tempId].base64;
                //  对tempUrl中的正则特殊字符进行转义和处理,newRegExp用转义后的字符串创建正则表达式，替换所有符合条件的内容
                console.log("正则转义后的tempUrl:",escapeRegExp(tempUrl))
                
                previewHtml = previewHtml.replace(new RegExp(escapeRegExp(tempUrl), 'g'), base64Url);

            });

            preview.innerHTML = previewHtml;

        }

        // 4. 监听输入事件（统一触发带图片的渲染）
        markdownInput.addEventListener('input', () => {

            console.log("触发输入事件");

            renderPreviewWithTempImages(markdownInput.innerText);

        });

        // 5. 基础Markdown渲染函数
        function renderMarkdown(markdown) {
            markdown = markdown || '';
            let html = handleHeadings(markdown);    // 标题
            html = handleLine(html)                   // 横线  
            html = handleLists(html);               // 列表
            html = handleInlineElements(html);      // 行内样式（加粗/斜体）
            html = handleImages(html);              // 图片（新增）
            html = handleLineBreaks(html);          // 换行
            return html;
        }

        // 处理标题
        function handleHeadings(markdown) {
            return markdown.replace(/^(#{1,6})\s+(.*?)$/gm, (match, hashes, text) => {
                const level = hashes.length;
                return `<h${level}>${text}</h${level}>`;
            });
        }

        // 处理列表
        function handleLists(markdown) {
            let html = markdown
                .replace(/^[-*]\s+(.*?)$/gm, '<li>$1</li>')    // 无序列表
                .replace(/^(\d+)\.\s+(.*?)$/gm, '<li>$2</li>'); // 有序列表

            // 包裹无序列表
            html = html.replace(/(<li>.*?<\/li>)\s+(?=<li>)/gs, '$1');
            html = html.replace(/^(?!<ul|<ol)(<li>.*?<\/li>)+/gs, '<ul>\n$&\n</ul>');
            // 包裹有序列表（补充：区分有序/无序列表）
            html = html.replace(/^(\d+\.\s+.*?)$/gm, (match) => {
                if (!match.includes('<li>')) return match;
                return match.replace(/<ul>/, '<ol>').replace(/<\/ul>/, '</ol>');
            });

            return html;
        }

        // 处理行内元素（加粗/斜体）
        function handleInlineElements(html) {
            return html
                .replace(/\*\*\*(.*?)\*\*\*/g, '<strong><em>$1</em></strong>') // 加粗斜体
                .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')               // 加粗
                .replace(/\*(.*?)\*/g, '<em>$1</em>')                           // 斜体（*）
                .replace(/_(.*?)_/g, '<em>$1</em>')                             // 斜体（_）
                .replace(/__(.*?)__/g, '<strong>$1</strong>');                  // 加粗（__）
        }

        // 处理图片（新增）
        function handleImages(markdown) {
            
            return markdown.replace(/!\[([^\]]*)\]\(([^"\s)]+)(\s+"[^"]*")?\)/g,
                (match, altText, src, title) => {
                    const titleAttr = title ? ` title="${title.trim().replace(/^"/, '').replace(/"$/, '')}"` : '';
                    return `<img src="${src}" alt="${altText}"${titleAttr} style="max-width:100%;height:auto;margin:10px 0;">`;
                }
            );
        }

        // 处理换行
        function handleLineBreaks(html) {
            return html.replace(/([^>])\n/g, '$1<br>\n');
        }


        function handleLine(html) {
            return html.replace(/^\s*[-*_]{3,}\s*(\n|$)/gm, '<hr>');
        }
        // 注释: ^ 匹配一行的开头
        // \s* 匹配0个或多个空白字符
        // [-*_]{3,}匹配至少3个连续的-,*,_
        // \s* 匹配0个或多个空白字符
        // (\n|$) 匹配换行符号(\n)或字符串结尾($),确保这是一整行的分割符
        // g 全局匹配
        // m 多行匹配
    </script>
</body>

</html>